# -*-coding: utf-8 -*-
"""
作者：TianyiFan
日期：2023-年07-月19日
用途：生成算法初始解
"""
import time
import para as params
import math
import copy
import numpy as np
import common_fuc
from submodel import submodel_gurobi
from submodel import submodel_cplex


class initial_solution(object):
    def __init__(self):
        # 初始解对象的信息
        self.obj = params.max_bound
        self.feasible = True
        self.gamma_ik = np.zeros([params.V + 2, params.K1], dtype=float)
        self.alpha_ih_u = np.zeros([params.V + 2, params.H], dtype=float)
        self.alpha_ih_l = np.zeros([params.V + 2, params.H], dtype=float)
        self.beta_lik = np.zeros([params.V + 2, params.K1], dtype=float)
        self.beta_uik = np.zeros([params.V + 2, params.K1], dtype=float)
        self.obj_vessel = np.zeros(params.V+2, dtype=float)
        self.num_subblock_v = np.zeros(params.V+2,dtype=int)
        self.list_gamma = {}                 # 每艘船分配了哪几个堆场
        self.vessel_block_floor_map = {}
        # key = vessel_index, value = {}: 每艘船分别在下层0.上层1，分配了哪几个堆场, 船的索引：1-V+1
        self.decision_seq_for_subblock = []   # 决策subblock分配的顺序，依据container的数量,存放pair对
        self.decision_seq_for_container = []  # 决策container装载的顺序，依据总平均成本作为权重，存放key

    def get_nearest_block(self, vessel_index):
        '''
        根据距离升序，得到vessel_index的泊位，分上下两层
        :param vessel_index:
        :return:
        up_subblock:上层的堆场排序，list
        down_subblock:下层的堆场排序,list
        '''
        up_subblock, down_subblock = [], []
        sort_up, sort_down = {}, {}
        berth_idx = params.berth_i[vessel_index]
        for k in range(params.K0):
            sort_down[k] = params.subblock_info[k].distance_berth[berth_idx] + params.subblock_info[k].unloading_distance[berth_idx]
        for k in range(params.K0, params.K1):
            sort_up[k] = params.subblock_info[k].distance_berth[berth_idx] + params.subblock_info[k].unloading_distance[berth_idx]
        down_set = sorted(sort_down.items(), key=lambda x: x[1], reverse=False)
        up_set = sorted(sort_up.items(), key=lambda x: x[1], reverse=False)
        for pair in up_set:
            up_subblock.append(pair[0])
        for pair in down_set:
            down_subblock.append(pair[0])
        return up_subblock, down_subblock

    def select_subblock(self, gap_k, gap_tk, gap_g, floor_index, sort_subblock_list, vessel_index):
        '''
        决策vessel_index 在 floor_index 的堆场分配，并更新gap_k.gap_tk,gap_g,self.ganma_k
        :param gap_k:堆场k是否被占用
        :param gap_tk:t时刻，堆场k是否被占用
        :param gap_g:
        :param floor_index:
        :param sort_subblock_list:
        :param vessel_index:
        :return: None
        '''
        assign_num = 0
        bound = params.r_ih[vessel_index][floor_index]
        # print('开始vessel-{}  floor={}  bound={}  '.format(vessel_index,floor_index,bound,sort_subblock_list))
        for candidate_k in sort_subblock_list:
            # print('vessel-{} assign_num-{}'.format(vessel_index,assign_num))
            # 如果分配给k的堆场已超过，或者已经满足max(2,最小堆场需求)，则分配停止---最少为每个堆场分配2个，便于双层同时进行装卸
            if assign_num >= bound or self.num_subblock_v[vessel_index]>= max(2,math.ceil(params.test_data.data.iloc[vessel_index-1]['num_container']/240)):
                # print('vessel-{} floor_index={}  assign_num-{} 停止分配！'.format(vessel_index,floor_index,assign_num))
                break
            condition1 = False
            condition2 = False
            condition3 = False
            g_index = params.subblock_info[candidate_k].blockbelong
            # print('candidate_k ={}   neibor_set={}'.format(candidate_k, pair_list))
            # 条件1：只能选择gap_k=0的堆场------------对应每个堆场只能分配给1艘船
            if gap_k[candidate_k] == 0:
                condition1 = True
            else:
                # print('vessel-{} gap_k={} 条件不满足'.format(vessel_index,candidate_k))
                continue
            # 条件2：只能选择当前gap_g<=2的堆场,不然该k不能占用
            ans = 0
            # 判断对应handle时间段内，第g个block内占用堆场的数量<=2的时间段数量,满足条件的step数量必须等于总的处理时长
            for t in params.test_data.data.iloc[vessel_index - 1]['handle_list']:
                if gap_g[g_index, t] <= 2:
                    ans += 1
            if ans == len(params.test_data.data.iloc[vessel_index - 1]['handle_list']):
                condition2 = True
            else:
                continue

            # 条件3：判断每一个时间t，是否neighbor pair中已经有其他的k被占用，只能选择gap_n<=1的堆场,不然该k不能占用
            pair_list = params.neighbor_set[candidate_k]
            for t in params.test_data.data.iloc[vessel_index - 1]['handle_list']:
                count = 0
                # 累加对应pair_list
                for temp_k in pair_list:
                    if gap_tk[temp_k, t] == 1:
                        count += 1
                if count >= 1:
                    break
                if t == params.test_data.data.iloc[vessel_index - 1]['handle_list'][-1]: #　若一直遍历到最后一个step仍然没问题，说明可以选该k
                    condition3 = True

            if condition3 is False:
                continue

            if condition1 and condition2 and condition3:
                self.gamma_ik[vessel_index, candidate_k] = 1
                # 更新全局数组
                gap_k[candidate_k] = 1
                for t in params.test_data.data.iloc[vessel_index - 1]['handle_list']:
                    gap_tk[candidate_k, t] = 1
                    gap_g[g_index, t] += 1
                assign_num += 1
                self.num_subblock_v[vessel_index]+=1
                # if vessel_index == 12:
                #     print('分配堆场-{0} 给vessel-{1}'.format(candidate_k, vessel_index))
                #     a = input("是否继续")
            else:
                continue
    def sequence_strategy_1(self):
        '''
        序列优先性决策策略1：根据unloading和loading总量，降序排序，得到一个顺序
        :return:
        '''
        decision_seq = {}
        for i in range(1, params.V + 1):
            decision_seq[i] = params.c_ui[i] + params.c_li[i]
        return sorted(decision_seq.keys(), key=lambda x: decision_seq[x], reverse=True)  # 得到一个vessel决策序列的列表

    def decide_subblock_assign(self):
        '''
        根据轮船的决策优先序列，优先决策每艘船上下层堆场分配的主函数
        :return: 更新self.list_gamma[vessel_index]
        '''
        # step1：for i in sequence，按照就近原则分配堆场
        gap_k = np.zeros(params.K1, dtype=int)                       # 单个subblock是否被占用
        gap_tk = np.zeros([params.K1, params.T], dtype=int)   # 在t时刻，subblock k 是否被占用 --- 结合handle_K 确定0-1关系
        gap_g  = np.zeros([params.G, params.T], dtype=int)    # 在t时刻，单个block内的占用数量
        for pair in self.decision_seq_for_subblock:   # 决策序列
            vessel_index = pair  # 获取船的编号
            # step1：获得船i的上层泊位序列，和下层泊位序列，按照距离原则
            up_subblock, down_subblock = self.get_nearest_block(vessel_index)
            # todo 根据r_ih的数量以及时间轴上决策，上层分配几个，下层分配几个，然后根据就近原则进行挑选
            #  应当需要根据上层数量和下层数量的多少来决定先分配上层的，还是分配下层的，且需要判定当前预留堆是否大于总需求数；
            # print('vessel_{0} floor={2} list={1}'.format(vessel_index,down_subblock,0))
            # print('vessel_{0} floor={2} list={1}'.format(vessel_index, up_subblock, 1))
            if params.r_ih[vessel_index,0] > params.r_ih[vessel_index,1]:
                self.select_subblock(gap_k, gap_tk, gap_g, 1, up_subblock, vessel_index)
                self.select_subblock(gap_k, gap_tk, gap_g, 0, down_subblock, vessel_index)
            else:
                self.select_subblock(gap_k, gap_tk, gap_g, 0, down_subblock, vessel_index)
                self.select_subblock(gap_k, gap_tk, gap_g, 1, up_subblock, vessel_index)

            assign_block = []
            for k in range(params.K1):
                if self.gamma_ik[vessel_index, k] == 1:
                    assign_block.append(k)
            self.list_gamma[vessel_index] = assign_block  # 第v艘船的分配block
            dict_map = {}
            dict_map[0], dict_map[1] = [], []
            for k in self.list_gamma[vessel_index]:
                if k<params.K0:
                    dict_map[0].append(k)
                else:
                    dict_map[1].append(k)
            self.vessel_block_floor_map[vessel_index] = dict_map
        # print('堆场分配决策结果：\n', self.list_gamma)
        # print('堆场分配细分结果：\n', self.vessel_block_floor_map)
        for i in range(1,params.V+1):
            print(f"vessel-{i:2}  预留subblock max={sum(params.r_ih[i])} min={math.ceil(params.test_data.data.iloc[i-1]['num_container']/240)} ：",end=" ")
            for h in range(2):
                for k in self.vessel_block_floor_map[i][h]:
                    print("{0:3}".format(k),end=" ")
            print()

    def decide_vessel_seq(self):
        '''
        根据每艘船的装卸数量，得到堆场装卸数量分配决策序列
        :return:self.decision_seq_for_container ---
        '''
        weigh_vessel = {}
        for vessel, list_block in self.list_gamma.items():
            average_loading = sum([params.t_lki[k][vessel] for k in list_block]) / len(list_block)
            average_loading_cost = average_loading * params.c_li[vessel]
            average_unloading = sum([params.t_uki[k][vessel] for k in list_block]) / len(list_block)
            average_unloading_cost = average_unloading * params.c_ui[vessel]
            weight = (average_loading_cost + average_unloading_cost) / 2
            # print(f'vessel-{vessel}  loading平均成本={average_loading_cost:.2f} unloading平均成本={average_unloading_cost:.2f}  权重：{weight:.2f}')
            weigh_vessel[vessel] = weight
            self.decision_seq_for_container = sorted(weigh_vessel.keys(), key=lambda x: weigh_vessel[x], reverse=True)
        print("集装箱堆场侧分配决策序列：\n", self.decision_seq_for_container)

    def min_store_space(self,vessel_index):
        space_h = np.zeros(2)
        for h in range(2):
            for k in self.vessel_block_floor_map[vessel_index][h]:
                space_h[h] += self.beta_lik[vessel_index][k]+self.beta_uik[vessel_index][k]
            space_h[h] = params.z*len(self.vessel_block_floor_map[vessel_index][h]) - space_h[h]
        # 返回的是第h层的剩余可用空间
        return space_h

    def assign_container_in_yard(self, vessel_index, stage):
        '''
        根据stage，计算vessel的beta_ik值
        :param vessel_index:
        :param stage:如果是loading就决策beta_lik,如果是unloading，就决策beta_uik
        :return:改变对应的决策变量
        '''
        decision_variable = [self.beta_lik,self.beta_uik]
        if stage == 'loading':
            variable = decision_variable[0]
            first_num = params.c_li[vessel_index]
        else:
            variable = decision_variable[1]
            first_num = params.c_ui[vessel_index]

        # 计算现有的堆区存量---定义一个函数，返回一个列表，存放上下两层当前的剩余存量
        container_max = self.min_store_space(vessel_index)
        # print(f'vessel = {vessel_index}  上下层的剩余存量：{container_max}')
        if min(container_max) == container_max[0]:
            less_floor, much_floor = 0, 1
        else:
            less_floor, much_floor = 1, 0
        if first_num <= min(container_max)*2:  # 上下层都可以平均分配一半的container，采用就近原则----需要根据现有的堆区存量来判断
            # 根据奇偶决定每层的分配数
            target_h = np.zeros(2)
            if first_num % 2==0:
                target_h[0], target_h[1] = first_num/2, first_num/2
            else:
                target_h[0], target_h[1] = first_num // 2, first_num // 2 + 1  # 奇数时，上层多1个
            # 就近填满每一层的堆区，首先判断该堆区是否有剩余的空间
            for h in range(2):
                for k in self.vessel_block_floor_map[vessel_index][h]:
                    available_space = params.z - (self.beta_lik[vessel_index][k] + self.beta_uik[vessel_index][k])
                    if target_h[h] == 0:
                        break
                    if available_space == 0:  # 已经占满-换下一个
                        continue
                    if target_h[h] <= available_space:
                        variable[vessel_index][k] += target_h[h]
                        target_h[h] = 0  # 全部排完
                    else:
                        variable[vessel_index][k] += available_space
                        target_h[h] -= available_space  # 扣掉当前的剩余空间还剩，需要继续填充

                if target_h[h] != 0:
                    print(f'vessel-{vessel_index} floor-{h}  还剩-{target_h[h]} 待排  stage={stage}！！！')
        else:  # 存量少的层全部填满，剩余的全部给另一层
            target_h = np.zeros(2)
            target_h[0],target_h[1] = min(container_max),min(container_max)  # 平均分的量
            target_1 = first_num - min(container_max)*2                      # 平均分完的剩余数量
            for h in range(2):
                for k in self.vessel_block_floor_map[vessel_index][h]:
                    available_space = params.z - (self.beta_lik[vessel_index][k] + self.beta_uik[vessel_index][k])
                    # print(f'subblock={k}  可用空间={available_space}  stage={stage}')
                    if target_h[h] == 0:
                        break
                    if available_space == 0:  # 已经占满-换下一个
                        continue
                    if target_h[h] < available_space:
                        variable[vessel_index][k] += target_h[h]
                        target_h[h]= 0  # 全部排完
                    else:
                        variable[vessel_index][k] += available_space
                        target_h[h] -= available_space  # 扣掉当前的剩余空间还剩，需要继续填充
            for k in self.vessel_block_floor_map[vessel_index][much_floor]:
                available_space = params.z - (self.beta_lik[vessel_index][k] + self.beta_uik[vessel_index][k])
                if target_1 == 0:
                    break
                if available_space == 0:  # 已经占满-换下一个
                    continue
                if target_1 < available_space:
                    variable[vessel_index][k] += target_1
                    target_1 = 0  # 全部排完
                else:
                    variable[vessel_index][k] += available_space
                    target_1 -= available_space # 还剩，需要继续找
            if target_1 != 0 :
                print(f'vessel-{vessel_index} 还剩-{target_1} 待排  stage={stage}！！！')

    def cal_beta_value(self,vessel_index,stage):
        '''
        计算vessel上下两层在堆场侧的装卸总数beta_uik、beta_lik
        :param vessel_index:
        :param stage:如果是loading就决策beta_lik,如果是unloading，就决策beta_uik
        :return:
        '''
        decision_variable = [self.beta_lik, self.beta_uik]
        if stage == 'loading':
            variable = decision_variable[0]
        else:
            variable = decision_variable[1]
        value_h = np.zeros(2)
        for h in range(2):
            for k in self.vessel_block_floor_map[vessel_index][h]:
                value_h[h] += variable[vessel_index][k]   # 在第h层的装卸量总数
        return value_h

    def cal_available_gap(self,vessel_index,gap_t):
        '''
        根据当前的时间轴转运配额，得到最大可转运量，按照其时间覆盖范围取其中的最小值
        :param vessel_index:
        :param gap_t:
        :return: available_gap--int 单位时间内最大的转运数量
        '''
        available_gap = params.C
        for t in params.test_data.data.iloc[vessel_index - 1]['handle_list']:
            available_gap = min(available_gap, gap_t[t])
        return available_gap

    def assign_container_in_berth(self, vessel_index, stage, gap_t, beta_value_h):
        '''
        计算每艘船loading/unloading阶段在泊位侧的装卸量，尽可能让每艘船上下层的装卸量平衡
        :param vessel_index:轮船索引
        :param stage:装卸船的阶段
        :param gap_t:单位时间内的转运量
        :param beta_value_h:
        :return:
        '''
        decision_variable = [self.alpha_ih_l, self.alpha_ih_u]
        if stage == 'loading':
            variable = decision_variable[0]
            num_container = params.c_li[vessel_index]
        else:
            variable = decision_variable[1]
            num_container = params.c_ui[vessel_index]
        if max(beta_value_h) == beta_value_h[0]:  # 确定层数较多的一层，多余的alpha优先在该层装卸
            much_floor, less_floor = 0, 1
        else:
            much_floor, less_floor = 1, 0
        available_gap = self.cal_available_gap(vessel_index, gap_t)  # 计算该船单位时间内的平均剩余容量

        if abs(beta_value_h[much_floor]-beta_value_h[less_floor])<=1:
            compare1 = 0  # 不需要转运
        else:
            compare1 = math.ceil((beta_value_h[much_floor] - (num_container / 2)) / params.handle_i[vessel_index])   # 理想的单位时间转运数量
        compare2 = available_gap
        # print(f'决策vessel_{vessel_index:2} stage={stage:10}  理想的单位时间转运数={compare1:2}   '
        #       f'实际的单位时间转运数={compare2:3}  handle_i={params.handle_i[vessel_index]}  beta_value_h={beta_value_h}')
        delta_max = min(compare1, compare2)
        if delta_max == compare1:  # 若能够满足理想的单位时间转运量
            if num_container % 2 == 0:
                num_much,num_less = num_container//2,num_container//2  # 上层==下层装卸==num_container/2
            else:
                num_much,num_less = num_container//2 +1, num_container//2 # 上层多1，下层num/2
            variable[vessel_index][much_floor] = num_much
            variable[vessel_index][less_floor] = num_less
            for t in params.test_data.data.iloc[vessel_index - 1]['handle_list']:
                gap_t[t] -= compare1
                # print(f'timestep-{t}  剩余转运量={gap_t[t]}')

    def decide_container_assign(self):
        '''
        计算每艘船loading/unloading阶段在堆场侧的装卸量，根据就近原则，将不同的container分配到最近的几个堆场中
        :return:
        '''
        # Todo 决策每艘船的上下层装卸量
        # step1：基于unloading和loading需求量*平均装卸距离作为权重排序轮船，得到装卸量决策的决策序列
        self.decide_vessel_seq()

        # step2: 根据loading和unloading的数量，先判断哪个过程更重要，决策beta_u/lik---先分配对应的堆场，然后再用剩余的量装另一个过程
        for vessel in self.decision_seq_for_container:
            first_decision_num = max(params.c_li[vessel], params.c_ui[vessel])
            second_decision_num = min(params.c_li[vessel], params.c_ui[vessel])
            stage = ''
            if first_decision_num == params.c_li[vessel]:
                # print(f'现在开始决策vessel={vessel} loading过程的集装箱分配----------')
                self.assign_container_in_yard(vessel,'loading')
                # print(f'现在开始决策vessel={vessel} unloading过程的集装箱分配----------')
                self.assign_container_in_yard(vessel,'unloading')
                stage='loading-to-unloading'
            else:
                # print(f'现在开始决策vessel={vessel} unloading过程的集装箱分配----------')
                self.assign_container_in_yard(vessel, 'unloading')
                # print(f'现在开始决策vessel={vessel} loading过程的集装箱分配----------')
                self.assign_container_in_yard(vessel, 'loading')
                stage = 'unloading-to-loading'
            # print('vessel-{0}   subblock={1}   stage={2}  c_li={3}   c_ui={4}'.format(vessel,self.list_gamma[vessel],stage,params.c_li[vessel],params.c_ui[vessel]))
            # for k in self.vessel_block_floor_map[vessel][0]:
            #     print('第0层  subblock = {0:2}   beta_lik={1}  beta_uik={2}'.format(k, self.beta_lik[vessel][k], self.beta_uik[vessel][k]))
            # for k in self.vessel_block_floor_map[vessel][1]:
            #     print('第1层 subblock = {0:2}   beta_lik={1}  beta_uik={2}'.format(k, self.beta_lik[vessel][k], self.beta_uik[vessel][k]))
            # a = input('是否继续执行：')

        # step3: 根据loading和unloading的数量，先判断哪个过程更重要，决策alpha_ik--尽可能平均放到上下两层，但同时要考虑gap_t的约束
        gap_t = np.zeros(params.T, dtype=int)  # 记录不同时刻的转运数量
        for t in range(params.T):
            gap_t[t] = params.C   # 单位时间内的最大转运量=C
        for vessel in self.decision_seq_for_container:
            # todo step1：计算vessel上下两层的装卸总数beta_uik、beta_lik
            beta_value_unloading = self.cal_beta_value(vessel,'unloading')
            beta_value_loading = self.cal_beta_value(vessel,'loading')
            first_decision_num = max(params.c_li[vessel], params.c_ui[vessel])
            second_decision_num = min(params.c_li[vessel], params.c_ui[vessel])
            if first_decision_num == params.c_li[vessel]:
                # print(f'现在开始决策vessel={vessel} loading过程的alpha_ih决策  beta_value_loading={beta_value_loading}----------')
                self.assign_container_in_berth(vessel, 'loading',gap_t,beta_value_loading)
                # print(f'现在开始决策vessel={vessel} unloading过程的alpha_ih决策  beta_value_unloading={beta_value_unloading}----------')
                self.assign_container_in_berth(vessel, 'unloading',gap_t,beta_value_unloading)
                stage = 'loading-to-unloading'
            else:
                # print(f'现在开始决策vessel={vessel} unloading过程的alpha_ih决策  beta_value_unloading={beta_value_unloading}----------')
                self.assign_container_in_berth(vessel, 'unloading',gap_t,beta_value_unloading)
                # print(f'现在开始决策vessel={vessel} loading过程的alpha_ih决策    beta_value_loading={beta_value_loading}----------')
                self.assign_container_in_berth(vessel, 'loading', gap_t,beta_value_loading)
                stage = 'unloading-to-loading'
            # print('vessel-{0}  c_li={1}  c_ui={2}'.format(vessel,params.c_li[vessel],params.c_ui[vessel]))
            # print(f'alpha_ui0 = {self.alpha_ih_u[vessel][0]}  alpha_ui1= {self.alpha_ih_u[vessel][1]}  beta_value_u = {beta_value_unloading} ')
            # print(f'alpha_li0 = {self.alpha_ih_l[vessel][0]}  alpha_ui1= {self.alpha_ih_l[vessel][1]}  beta_value_u = {beta_value_loading} ')

        # step4: 进行规程检查，判断是否每个time_t 的gap_t均被满足
        result,info = self.check_gap_t()
        if result:
            print('转运数量检查完毕！符合约束')
        else:
            self.feasible = False
            print(f'转运数量检查完毕，存在违反约束的time-step: \n {info}')

    def check_gap_t(self):
        '''
        对单位时间内的转运数量进行规程检查，防止出现数量错误
        :return: 检查结果是否正确，bool+不满足的t
        '''
        info = []  # 代表不满足转运约束的t的列表
        gap_t = np.zeros(params.T,dtype=int)
        for i in range(1,params.V):
            total_num1 = 0
            total_num2 = 0
            for k in self.vessel_block_floor_map[i][1]:
                total_num1 += self.beta_lik[i][k]
                total_num2 += self.beta_uik[i][k]
            total_trans = abs(self.alpha_ih_l[i][1] - total_num1) + abs(self.alpha_ih_u[i][1] - total_num2)
            for t in params.test_data.data.iloc[i - 1]['handle_list']:
                gap_t[t] += math.ceil(total_trans // params.handle_i[i])
            if gap_t[t] > params.C:
                info.append({t: gap_t[t]})
        if len(info) == 0:
            return True, info
        else:
            return False, info

    def cal_objective(self):
        '''
        计算初始解的目标函数值,封装在初始解类里计算目标函数值的方法
        :return: 初始解的obj
        '''
        cost_P = 0 # 堆场卸载时间成本
        cost_U = 0 # 堆场转运时间成本
        cost_T = 0 # 堆场运输成本
        cost_K = 0 # 堆场占用成本
        obj_vessel = np.zeros(params.V+2,dtype=float)
        cost_P_i = np.zeros(params.V+2,dtype=float)
        cost_u_i = np.zeros(params.V + 2, dtype=float)
        cost_t_i = np.zeros(params.V + 2, dtype=float)
        count_K = 0
        for i in range(1,params.V+1):
            cost_P_i[i] = params.time_p*(max(self.alpha_ih_u[i])+max(self.alpha_ih_l[i]))
            total_num1 = 0
            total_num2 = 0


            for k in self.vessel_block_floor_map[i][1]:
                total_num1 += self.beta_lik[i][k]
                total_num2 += self.beta_uik[i][k]
            total_trans = abs(self.alpha_ih_l[i][1] - total_num1) + abs(self.alpha_ih_u[i][1] - total_num2)
            cost_u_i[i] = params.time_u*total_trans

            for h in range(2):
                for k in self.vessel_block_floor_map[i][h]:
                    cost_t_i[i] += self.beta_lik[i][k]*params.t_lki[k][i]+self.beta_uik[i][k]*params.t_uki[k][i]
            obj_vessel[i]=cost_t_i[i]+cost_P_i[i]+cost_u_i[i]
            self.obj_vessel[i] = obj_vessel[i]
            cost_P+=cost_P_i[i]
            cost_T+=cost_t_i[i]
            cost_U+=cost_u_i[i]
            if params.add_cost:
                count_K = len(self.vessel_block_floor_map[i][1]) + len(self.vessel_block_floor_map[i][0])
                cost_K+=count_K*params.add_cost

        obj = cost_P+cost_T+cost_U+cost_K
        print(f'初始解={obj/3600:.2f} hours  其中：装卸货时间={cost_P/3600:.2f} h  堆场转运时间={cost_U/3600:.2f} h   堆场运输时间={cost_T/3600:.2f} h')
        return obj

    def updata_rmp_plan(self,base,plan_index):
        '''
        将计划更新到存放计划的全局数组中---更新输入进来的一个base_pool
        :param base: 记录存放全局计划的数组
        :return:
        '''
        # 更新A_pi_k
        print("更新base中的第一个池子")
        for v in range(1,params.V+1):
            for h in range(2):
                for k in self.vessel_block_floor_map[v][h]:
                    base.A_pi_k[v,plan_index,k] = 1
        # 更新B_pig
        for v in range(1, params.V + 1):
            for h in range(2):
                for k in self.vessel_block_floor_map[v][h]:
                    g_index = params.subblock_info[k].blockbelong
                    # print(f'轮船-{v} 分配subblock-{k}  对应block={g_index}')
                    base.B_pig[v,plan_index, g_index] += 1

        # 更新D_pin
        for v in range(1, params.V + 1):
            for n in range(params.pair_N):
                for k in params.neighbor_pair[n]:
                    if k in self.vessel_block_floor_map[v][0] or k in self.vessel_block_floor_map[v][1]:
                        base.D_pin[v,plan_index, n] += 1
                        # print(f'轮船-{v} 分配subblock-{k}  对应neibor_pair={params.neighbor_pair[n]} 编号={n}')
        # 更新Fpi_l与更新Fpi_u
        for v in range(1, params.V+1):
            value_h1 = self.cal_beta_value(v,'loading')   # 装货时，堆场侧的装卸量
            value_h2 = self.cal_beta_value(v, 'unloading')  # 卸货时，堆场侧的装卸量
            delta_container1 = abs(self.alpha_ih_l[v, 0] - value_h1[0])  # 以第0层的装卸量之差作为delta
            delta_container2 = abs(self.alpha_ih_u[v, 0] - value_h2[0])
            base.Fpi_l[v,plan_index] =  delta_container1
            base.Fpi_u[v,plan_index] = delta_container2

        # 更新Cost_pi--每艘船初始计划的成本
        check_cost = 0
        for i in range(1,params.V+1):
            # 堆场装卸成本
            delta_l = max(self.alpha_ih_l[i])
            delta_u = max(self.alpha_ih_u[i])
            cost1 = params.time_p*(delta_l+delta_u)
            cost2 = params.time_u*(base.Fpi_l[i,plan_index]+base.Fpi_u[i,plan_index])
            cost3 = 0
            for k in range(params.K1):
                cost3+=params.t_uki[k,i]*self.beta_uik[i,k]+params.t_lki[k,i]*self.beta_lik[i,k]
            base.Cost_pi[i,0] = cost1+cost2+cost3
            check_cost+= base.Cost_pi[i,plan_index]
            print(f"初始解中-轮船-{i} 的Cost={base.Cost_pi[i,plan_index]:.2f}")
        print('基于初始解计划得到的cost={}'.format(check_cost))

    def value_update(self,model):
        '''
        将模型求解的结果赋值给当前类中的变量，只适用于在启发式生成堆场分配求解submodel中
        :param model:
        :return:
        '''
        for i in range(1,params.V+1):
            for k in range(params.K1):
                self.beta_uik[i,k] = model.beta_uik[i,k].x
                self.beta_lik[i,k] = model.beta_lik[i,k].x
            for h in range(params.H):
                self.alpha_ih_l[i,h] = model.alpha_lih[i,h].x
                self.alpha_ih_u[i,h] = model.alpha_uih[i,h].x

    def value_update2(self, solution_rd):
        '''
        将模型求解的结果赋值给当前类中的变量，只适用于在启发式生成堆场分配求解submodel中
        :param solution_rd: 记录全局解的类
        :return:
        '''
        for i in range(1, params.V + 1):
            for k in range(params.K1):
                self.beta_uik[i, k] = solution_rd.beta_uik[i, k]
                self.beta_lik[i, k] = solution_rd.beta_lik[i, k]
            for h in range(params.H):
                self.alpha_ih_l[i, h] = solution_rd.alpha_lih[i, h]
                self.alpha_ih_u[i, h] = solution_rd.alpha_uih[i, h]

    def rule_heuristic(self):
        '''
        生成初始解的主函数
        :return:
        '''
        # step1: 得到初始的决策序列
        self.decision_seq_for_subblock,temp_data = common_fuc.sequence_strategy_1(params.test_data.data)
        print("Rule堆场分配决策序列：\n", self.decision_seq_for_subblock)

        # step2: 根据决策序列，生成堆场分配方案
        self.decide_subblock_assign()

        # step3: 根据决策序列，得到堆场装卸量分配方案
        mode = 3
        if mode == 1:
            t1 = time.time()
            model1 = submodel_gurobi(self.gamma_ik)
            model1.operate_model()
            self.value_update(model1)
            t2 = time.time()
            print("submodel_gurobi-求解时间={:.2f}".format(t2-t1))
        elif mode==2:
            t1 = time.time()
            model2 = submodel_cplex()
            model2.build_model(self.gamma_ik)
            self.value_update2(model2.optimal_rd)
            t2 = time.time()
            print("submodel_cplex-求解时间={:.2f}".format(t2 - t1))
        else:
            self.decide_container_assign()   # 启发式分配货量
        if self.feasible:
            self.obj = self.cal_objective()
        else:
            self.obj = params.max_bound




